export interface DebounceAsyncFunction<TArgs extends any[], R = any> {
  (...args: TArgs): R;

  cancel: () => void;
  isPending: () => boolean;
}

export interface DebounceAsyncOptions {
  // 延迟调用时间
  delay: number;
  // 当处于pending状态时，调用这个回调返回默认值
  onPending?: () => any;
}

/**
 * 函数防抖
 * @param func
 * @param options
 */
export function debounceAsync<TArgs extends any[], R = any>(
  func: (...args: TArgs) => R,
  options: DebounceAsyncOptions,
): DebounceAsyncFunction<TArgs, R> {
  let timeoutId: any = null;
  let promise: Promise<R> | null = null;
  let resolveFn: null | ((...args: any[]) => void) = null;
  let rejectFn: null | ((reason?: any) => void) = null;

  const cancel = () => {
    clearTimeout(timeoutId);
    timeoutId = null;

    if (resolveFn != null) {
      resolveFn(options.onPending?.());
      resolveFn = null;
    }
  };

  const fn = function debounceAsyncFn(...args: TArgs) {
    cancel();

    promise = new Promise<R>((resolve, reject) => {
      resolveFn = resolve;
      rejectFn = reject;

      timeoutId = setTimeout(() => {
        timeoutId = null;
        try {
          const ret = func(...args);
          if (ret instanceof Promise) {
            ret.then(resolve).catch(reject);
          } else {
            resolve(ret);
          }
        } catch (e) {
          reject(e);
        } finally {
          promise = null;
          resolveFn = null;
          rejectFn = null;
        }
      }, options.delay);
    });
    return promise;
  } as DebounceAsyncFunction<TArgs, R>;

  fn.cancel = cancel;

  fn.isPending = () => timeoutId !== null;

  return fn;
}
